<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1, minimum-scale=1, maximum-scale=1">
<meta name="renderer" content="webkit">
<meta name="google" value="notranslate">
<meta name="robots" content="index,follow">


<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Akkuman">
<meta name="twitter:description" content="Akkuman的技术博客">
<meta name="twitter:image:src" content="http://127.0.0.1:8000/images/avatar.png">

<meta property="og:url" content="http://127.0.0.1:8000">
<meta property="og:title" content="Akkuman">
<meta property="og:description" content="Akkuman的技术博客">
<meta property="og:site_name" content="Akkuman">
<meta property="og:image" content="http://127.0.0.1:8000/images/avatar.png">
<meta property="og:type" content="website">
<meta name="robots" content="noodp">

<meta itemprop="name" content="Akkuman">
<meta itemprop="description" content="Akkuman的技术博客">
<meta itemprop="image" content="http://127.0.0.1:8000/images/avatar.png">

<link rel="canonical" href="http://127.0.0.1:8000">

<link rel="shortcut icon" href="/favicon.png">
<link rel="apple-itouch-icon" href="/favicon.png">

<link type="text/css" rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/prism.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/zoom.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/main.css">
<script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>



<script>var cPlayers = [];var cPlayerOptions = [];</script>


<script type="text/javascript">
    var timeSinceLang = {
        year: '年前',
        month: '个月前',
        day: '天前',
        hour: '小时前',
        minute: '分钟前',
        second: '秒前'
    };
    var root = '';
</script>


        <meta name="keywords" content="ASM,读书笔记,Windows,汇编(ASM),">
        <meta name="description" content="Win32汇编学习(4)：绘制文本">
        <meta name="author" content="Akkuman">
        <title>Win32汇编学习(4)：绘制文本</title>
    </head>
    <body>
        
        <header id="header" class="clearfix">
  <div class="container-fluid">
      <div class="row">
          <div class="logo">
              <div class="header-logo">
                <script>
                  var getwbclass = function() {
                    var wbclass = ['b', 'w'];
                    return wbclass[Math.floor(Math.random()*wbclass.length)];
                  }
                  var sitetitle = "Akkuman";
                  for (i in sitetitle) {
                    document.write('<a href="/"><span class="' + getwbclass() + ' titlechar">' + sitetitle.charAt(i) + '</span></a>');
                  }          
                  
                </script>
                
                <a id="btn-menu" href="javascript:isMenu();">
                    <span class="b">·</span>
                </a>
                <a href="javascript:isMenu1();">
                    <span id="menu-1" class="bf">1</span>
                </a>
                <a href="javascript:isMenu2();">
                    <span id="menu-2" class="bf">2</span>
                </a>
                <a href="javascript:isMenu3();">
                    <span id="menu-3" class="bf">3</span>
                </a>
              </div>
              <div id="menu-page">
                <a href="/archive.html"><li>归档</li></a>
                <a href="/tag.html"><li>标签</li></a>
                
                <a href="/atom.xml"><li>订阅</li></a>
                
                <a href="about.html"><li>关于</li></a>
              </div>
              <div id="search-box">
                  <div id="search">
                      <input autocomplete="off" type="text" name="s" id="menu-search" placeholder="搜索..." data-root="" />
                  </div>
              </div>
          </div>
      </div>
  </div>
  </header>
        <div id="body" class="clearfix">
            <div class="container-fluid">
                <div class="row">
                    <div id="main" class="col-12 clearfix" role="main">
                        <article class="posti" itemscope itemtype="http://schema.org/BlogPosting">
                            <h1 class="post-title" itemprop="name headline">Win32汇编学习(4)：绘制文本</h1>
                            <div class="post-meta">
                                <p>
                                    Written by <a itemprop="name" href="/about.me.html" rel="author">Akkuman</a> with ♥ on <time datetime="1517994685" itemprop="datePublished"></time> in <a href="/tag/ASM/index.html">ASM </a><a href="/tag/%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0/index.html">读书笔记 </a><a href="/tag/Windows/index.html">Windows </a><a href="/tag/%e6%b1%87%e7%bc%96%28ASM%29/index.html">汇编(ASM) </a>
                                </p>
                            </div>
                            <div class="post-content" itemprop="articleBody">
                                <p>这次，我们将学习如何在窗口的客户区“绘制”字符串。我们还将学习关于“设备环境”的概念。</p>

<h2>理论：</h2>

<h3>“绘制”字符串</h3>

<p>Windows 中的文本是一个GUI（图形用户界面）对象。每一个字符实际上是由许多的像素点组成，这些点在有笔画的地方显示出来，这样就会出现字符。这也是为什么我说“绘制”字符，而不是写字符。通常您都是在您应用程序的客户区“绘制”字符串（尽管您也可以在客户区外“绘制”）。Windows 下的“绘制”字符串方法和 Dos 下的截然不同，在 Dos 下，您可以把屏幕想象成 85 x 25 的一个平面，而 Windows 下由于屏幕上同时有几个应用程序的画面，所以您必须严格遵从规范。Windows 通过把每一个应用程序限制在他的客户区来做到这一点。当然客户区的大小是可变的，您随时可以调整。</p>

<p>在您在客户区“绘制”字符串前，您必须从 Windows 那里得到您客户区的大小，确实您无法像在 DOS 下那样随心所欲地在屏幕上任何地方“绘制”，绘制前您必须得到 Windows 的允许，然后 Windows 会告诉您客户区的大小，字体，颜色和其它 GUI 对象的属性。您可以用这些来在客户区“绘制”。</p>

<h3>设备环境</h3>

<p>什么是“设备环境”（DC）呢？ 它其实是由 Windows 内部维护的一个数据结构。一个“设备环境”和一个特定的设备相连。像打印机和显示器。对于显示器来说，“设备环境”和一个个特定的窗口相连。</p>

<p>“设备环境”中的有些属性和绘图有关，像：颜色，字体等。您可以随时改动那些缺省值，之所以保存缺省值是为了方便。您可以把“设备环境”想象成是Windows 为您准备的一个绘图环境，而您可以随时根据需要改变某些缺省属性。</p>

<p>当应用程序需要绘制时，您必须得到一个“设备环境”的句柄。通常有几种方法。</p>

<ul>
<li>在 <code>WM_PAINT</code> 消息中使用 <code>call BeginPaint</code></li>
<li>在其他消息中使用 <code>call GetDC</code></li>
<li><code>call CreateDC</code> 建立你自己的 DC</li>
</ul>

<p>您必须牢记的是，<strong>在处理单个消息后你必须释放“设备环境”句柄</strong>。不要在一个消息处理中获得 “设备环境”句柄，而在另一个消息处理中在释放它。</p>

<p>我们在Windows 发送 <code>WM_PAINT</code> 消息时处理绘制客户区，Windows 不会保存客户区的内容，它用的是方法是“重绘”机制（譬如当客户区刚被另一个应用程序的客户区覆盖），Windows 会把 <code>WM_PAINT</code> 消息放入该应用程序的消息队列。重绘窗口的客户区是各个窗口自己的责任，您要做的是在窗口过程处理 WM_PAINT 的部分知道绘制什么和何如绘制。</p>

<p>您必须了解的另一个概念是“无效区域”。Windows 把一个最小的需要重绘的正方形区域叫做“无效区域”。当 Windows 发现了一个”无效区域“后，它就会向该应用程序发送一个 <code>WM_PAINT</code> 消息，在 <code>WM_PAINT</code> 的处理过程中，窗口首先得到一个有关绘图的结构体，里面包括无效区的坐标位置等。您可以通过调用 <code>BeginPaint</code> 让“无效区”有效，<strong>如果您不处理 <code>WM_PAINT</code> 消息，至少要调用缺省的窗口处理函数 <code>DefWindowProc</code> ，或者调用 <code>ValidateRect</code> 让“无效区”有效。否则您的应用程序将会收到无穷无尽的 <code>WM_PAINT</code> 消息。</strong></p>

<p>下面是响应该消息的步骤：</p>

<ol>
<li>取得“设备环境”句柄</li>
<li>绘制客户区</li>
<li>释放“设备环境”句柄</li>
</ol>

<p>注意，您无须显式地让“无效区”有效，这个动作由 <code>BeginPaint</code> 自动完成。您可以在 <code>BeginPaint</code> 和 <code>Endpaint</code> 之间，调用所有的绘制函数。几乎所有的 GDI 函数都需要“设备环境”的句柄作为参数。</p>

<h2>内容：</h2>

<p>我们将写一个应用程序，它会在客户区的中心显示一行 &ldquo;Win32 汇编非常有意思&rdquo;</p>

<pre><code class="language-asm">.386 
.model flat,stdcall 
option casemap:none 

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

include \masm32\include\windows.inc 
include \masm32\include\user32.inc 
includelib \masm32\lib\user32.lib 
include \masm32\include\kernel32.inc 
includelib \masm32\lib\kernel32.lib 

.DATA 
ClassName db &quot;SimpleWinClass&quot;,0 
AppName  db &quot;Our Second Window&quot;,0 
OurText  db &quot;Win32 汇编非常有意思&quot;,0 

.DATA? 
hInstance HINSTANCE ? 
CommandLine LPSTR ? 

.CODE 
start: 
    invoke GetModuleHandle, NULL 
    mov    hInstance,eax 
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT 
    invoke ExitProcess,eax 

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 
    mov   wc.cbSize,SIZEOF WNDCLASSEX 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInst 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc 
    invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ 
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ 
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ 
           hInst,NULL 
    mov   hwnd,eax 
    invoke ShowWindow, hwnd,SW_SHOWNORMAL 
    invoke UpdateWindow, hwnd 
        .WHILE TRUE 
                invoke GetMessage, ADDR msg,NULL,0,0 
                .BREAK .IF (!eax) 
                invoke TranslateMessage, ADDR msg 
                invoke DispatchMessage, ADDR msg 
        .ENDW 
        mov     eax,msg.wParam 
        ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    LOCAL hdc:HDC 
    LOCAL ps:PAINTSTRUCT 
    LOCAL rect:RECT 
    .IF uMsg==WM_DESTROY 
        invoke PostQuitMessage,NULL 
    .ELSEIF uMsg==WM_PAINT 
        invoke BeginPaint,hWnd, ADDR ps 
        mov    hdc,eax 
        invoke GetClientRect,hWnd, ADDR rect 
        invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ 
                DT_SINGLELINE or DT_CENTER or DT_VCENTER 
        invoke EndPaint,hWnd, ADDR ps 
    .ELSE 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam 
        ret 
    .ENDIF 
    xor   eax, eax 
    ret 
WndProc endp 
end start 
</code></pre>

<h2>分析：</h2>

<p>这里的大多数代码和<code>Win32汇编学习(3)：简单的窗口</code>中的一样。我只解释其中一些不相同的地方。</p>

<pre><code class="language-asm">LOCAL hdc：HDC
LOCAL ps：PAINTSTRUCT
LOCAL rect：RECT
</code></pre>

<p>这些局部变量由处理 <code>WM_PAINT</code> 消息中的 GDI 函数调用。<code>hdc</code> 用来存放调用 <code>BeginPaint</code> 返回的“设备环境”句柄。<code>ps</code> 是一个 <code>PAINTSTRUCT</code> 数据类型的变量。通常您不会用到其中的许多值，它由 Windows 传递给 <code>BeginPaint</code>，在结束绘制后再原封不动的传递给 <code>EndPaint</code>。<code>rect</code> 是一个 <code>RECT</code> 结构体类型参数，它的定义如下：</p>

<pre><code>RECT Struct left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
</code></pre>

<p>left 和 top 是正方形左上角的坐标。right 和 bottom 是正方形右下角的坐标。客户区的左上角的坐标是 x=0，y=0，这样对于 x=0，y=10 的坐标点就在它的下面。</p>

<pre><code class="language-asm">invoke BeginPaint，hWnd， ADDR ps
mov hdc，eax
invoke GetClientRect，hWnd， ADDR rect
invoke DrawText， hdc，ADDR OurText，-1， ADDR rect， \ 
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint，hWnd， ADDR ps
</code></pre>

<p>在处理 <code>WM_PAINT</code> 消息时，您调用<code>BeginPaint</code>函数，传给它一个窗口句柄和未初始化的 <code>PAINTSTRUCT</code> 型参数。调用成功后在 eax 中返回“设备环境”的句柄。下一次，调用 <code>GetClientRect</code> 以得到客户区的大小，大小放在 <code>rect</code> 中，然后把它传给 <code>DrawText</code>。<code>DrawText</code> 的语法如下：</p>

<pre><code>DrawText proto hdc：HDC， lpString：DWORD， nCount：DWORD， lpRect：DWORD， uFormat：DWORD 
</code></pre>

<p><code>DrawText</code>是一个高层的调用函数。它能自动处理像换行、把文本放到客户区中间等这些杂事。所以您只管集中精力“绘制”字符串就可以了。让我们来看一看该函数的参数：</p>

<ul>
<li><code>hdc</code>： “设备环境”的句柄。</li>
<li><code>lpString</code>：要显示的文本串，该文本串要么以NULL结尾，要么在nCount中指出它的长短。</li>
<li><code>nCount</code>：要输出的文本的长度。若以NULL结尾，该参数必须是-1。</li>
<li><code>lpRect</code>： 指向要输出文本串的正方形区域的指针，该方形必须是一个裁剪区，也就是说超过该区域的字符将不能显示。</li>
<li><code>uFormat</code>：指定如何显示。我们可以用 or 把以下标志或到一块：

<ul>
<li>DT_SINGLELINE：是否单行显示。</li>
<li>DT_CENTER：是否水平居中。</li>
<li>DT_VCENTER ：是否垂直居中。</li>
</ul></li>
</ul>

<p>结束绘制后，必须调用 <code>EndPaint</code> 释放“设备环境”的句柄。 好了，现在我们把“绘制”文本串的要点总结如下：</p>

<ol>
<li>必须在开始和结束处分别调用 <code>BeginPaint</code> 和 <code>EndPaint</code>；</li>
<li>在 <code>BeginPaint</code> 和 <code>EndPaint</code> 之间调用所有的绘制函数；</li>
<li>如果在其它的消息处理中重新绘制客户区，您可以有两种选择：

<ul>
<li>用<code>GetDC</code>和<code>ReleaseDC</code>代替<code>BeginPaint</code>和<code>EndPaint</code>；</li>
<li>调用<code>InvalidateRect</code>或<code>UpdateWindow</code>让客户区无效，这将迫使WINDOWS把<code>WM_PAINT</code>放入应用程序消息队列，从而使得客户区重绘。</li>
</ul></li>
</ol>

                            </div>
                            <div style="display:block;" class="clearfix">
                                <section style="float:left;">
                                    <span itemprop="keywords" class="tags">
                                        tag(s): <a href="/tag/ASM/index.html">ASM </a><a href="/tag/%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0/index.html">读书笔记 </a><a href="/tag/Windows/index.html">Windows </a><a href="/tag/%e6%b1%87%e7%bc%96%28ASM%29/index.html">汇编(ASM) </a>
                                    </span>
                                </section>
                                <section style="float:right;">
                                    <span><a id="btn-comments" href="javascript:isComments();">show comments</a></span> · <span><a href="javascript:goBack();">back</a></span> · 
                                    <span><a href="/">home</a></span>
                                </section>
                            </div>
                            



<div id="comments" class="gen">
    <script>
        document.write('<section id="disqus_thread"></section>');
        var site_comment_load = function disqus() {
            var d = document, s = d.createElement('script');
            s.src = '//Akkum4n.disqus.com/embed.js';
            s.setAttribute('data-timestamp', +new Date());
            (d.head || d.body).appendChild(s);
        }
    </script>
</div>

                        </article>
                    </div>
                </div>
            </div>
        </div>
        <footer id="footer" role="contentinfo">
    <div class="container-fluid">
        <div class="row">
        <div class="col-12">
            &copy; 
            <script type="text/javascript">
                document.write(new Date().getFullYear());
            </script>
            <a href="/">Akkuman</a>.
            Using <a target="_blank" href="http://www.chole.io/">Ink</a> & <a target="_blank" href="/">Story</a>.
        </div>
        </div>
    </div>
</footer>

<script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="/bundle/js/prism.js"></script>
<script src="/bundle/js/zoom-vanilla.min.js"></script>
<script src="/bundle/js/main.js"></script>

<script>
    window.onload=function(){
        if (window.location.hash!='') {
          var i=window.location.hash.indexOf('#comment');
          var ii=window.location.hash.indexOf('#respond-post');
          if (i != '-1' || ii != '-1') {
            document.getElementById('btn-comments').innerText='hide comments';
            document.getElementById('comments').style.display='block';
          }
        }
    }

    function isMenu(){
        if(document.getElementById('menu-1').style.display=='inline'||document.getElementById('menu-1').style.display=='block'){
            $('#search-box').fadeOut(200);
            $('#menu-page').fadeOut(200);
            $('#menu-1').fadeOut(500);
            $('#menu-2').fadeOut(400);
            $('#menu-3').fadeOut(300);
        } else {
            $('#menu-1').fadeIn(150);
            $('#menu-2').fadeIn(150);
            $('#menu-3').fadeIn(150);
        }
    }

    function isMenu1(){
        if(document.getElementById('menu-page').style.display=='block'){
            $('#menu-page').fadeOut(300);
        } else {
            $('#menu-page').fadeIn(300);
        }
    }

    function isMenu2(){
        if(document.getElementById('torTree')){
            if(document.getElementById('torTree').style.display=='block'){
                $('#torTree').fadeOut(300);
            } else {
                $('#torTree').fadeIn(300);
            }
        }
    }

    function isMenu3(){
        if(document.getElementById('search-box').style.display=='block'){
            $('#search-box').fadeOut(300);
        } else {
            $('#search-box').fadeIn(300);
        }
    }

    function isComments(){
        if(document.getElementById('btn-comments').innerText=='show comments'){
            document.getElementById('btn-comments').innerText='hide comments';
            document.getElementById('comments').style.display='block';
            site_comment_load();
        } else {
            document.getElementById('btn-comments').innerText='show comments';
            document.getElementById('comments').style.display='none';
        }
    }

    function Search404(){
        $('#menu-1').fadeIn(150);
        $('#menu-2').fadeIn(150);
        $('#menu-3').fadeIn(150);
        $('#search-box').fadeIn(300);
    }

    function goBack(){
        window.history.back();
    }
</script>


<script async>
"use strict";
(function(){
var cp = function(){
    var len = cPlayerOptions.length;
    for(var i=0;i<len;i++){
        var element = document.getElementById('player' + cPlayerOptions[i]['id'])
        while (element.hasChildNodes()) {
            element.removeChild(element.firstChild);
        };
        cPlayers[i] = new cPlayer({
            element: element,
            list: cPlayerOptions[i]['list'],
            });
    };
    cPlayers = [];cPlayerOptions = [];
};
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "https://cdn.bootcss.com/cplayer/3.2.1/cplayer.js";
script.async = true;
if(script.readyState){  
    script.onreadystatechange = function(){
        if (script.readyState == "loaded" ||
            script.readyState == "complete"){
            script.onreadystatechange = null;
            cp();
        }
    };
}else{  
    script.onload = function(){
        cp();
    };
}
document.head.appendChild(script);
})();
</script>

    </body>
</html>
